classdef dotsAllManagedObjects < handle
    % @class dotsAllManagedObjects
    % Superclass for objects that focus on specific behaviors and allow
    % managers to coordinate them with others.
    % @details
    % dotsAllManagedObjects provides a uniform interface for objects that
    % allow their behaviors to be coordinated by "manager" objects.
    % dotsAllManagedObjects makes a contract with the dotsAllObjectManagers
    % superclass.
    % @details
    % <b>Division of Labor</b>
    % @details
    % Snow Dots distinguishes between individual object behaviors and the
    % coordination of behaviors to form a system.  This allows for a
    % division of labor.  See dotsAllObjectManagers for a description of
    % the division of labor and the contract that makes it explicit.
    % @details
    % <b>Focused Behaviors</b>
    % @details
    % A managed object's job is to focus on a specific behavior without
    % worrying about system-level coordination.  For example:
    %   - A stimulus class might have a draw() or play() method for
    %   presenting its stimulus.  It should only "know how" to present the
    %   stimulus and leave it up to the manager when, where, and on what
    %   machine to present the stimulus.
    %   - An input class might use a unique piece of hardware, such as an
    %   eyetracker.  Creating multiple instances of the same class might
    %   cause competition for the same resources--and errors. Nevertheless,
    %   the class should only "know how" to create a new instance and let
    %   the manager worry about uniqueness and how many instances to
    %   create.
    %   - A managed object should not need to "know" whether it's being by
    %   itself or along with other managed objects.
    %   .
    % dotsAllManagedObjects itself is not a usable class, it just provides
    % a uniform interface and core functionality to subclasses (see below).
    % @ingroup foundataion
    
    properties (SetObservable=false)
        % the dotsAllObjectManagers singleton that is managing this object
        % @details
        % The manager automatically sets this when it creates a managed
        % object.  See the newObject() method of dotsAllObjectManagers.
        manager;
        
        % a unique identifier for this object
        % @details
        % The manager automatically sets this when it creates a managed
        % object.  See the newObject() method of dotsAllObjectManagers.
        % @details
        % The identifier may need to be unique across multiple machines.
        UID;
        
        % cell array of strings identifying groups this object belongs to
        % @details
        % The manager automatically adds and removes groups during a
        % managed object's lifecycle.  See the newObject(), removeObject(),
        % addObjectToGroup(), and removeObjectFromGroup() methods of
        % dotsAllObjectManagers.
        groups = {};
    end
    
    properties (SetObservable=false, Access=protected)
        % struct of event.listener objects
        % @details
        % Each field of propertyChangeListeners has the same name as a
        % SetObservable property of this object and contains an
        % event.listener for the same property.
        propertyChangeListeners;
    end
    
    methods
        % Constructor takes no arguments.
        % Users should not use dotsAllManagedObjects object constructors
        % directly.  See the newObject() method of dotsAllObjectManagers.
        function self = dotsAllManagedObjects
            self.propertyChangeListeners = struct;
        end
        
        % Set a property without reporting it to the manager.
        % @param property the string name of a SetObservable property of
        % this object
        % @param value the value to assign to @a property
        % @details
        % Temporarily disables the property listener for @a property while
        % assigning the new @a value.  The change will not be reported to
        % the object's manager.
        % @details
        % setPropertySilently() only works for SetObservable properties of
        % the object.
        % @details
        % Users should not use setPropertySilently() but managers may call
        % it internally.
        function setPropertySilently(self, property, value)
            listener = self.propertyChangeListeners.(property);
            listener.Enabled = false;
            self.(property) = value;
            listener.Enabled = true;
        end
        
        % Activate listeners for this object's properties.
        % @param manager the dotsAllObjectManagers singleton that will
        % manage this object
        % @details
        % Assigns @a manager to this object's manager property and
        % activates listeners that will report chagnes to this objects
        % SetObservable properties to @a manager.
        % @details
        % Users should not use startReportingPropertiesToManager() but
        % managers may call it internally.
        function startReportingPropertiesToManager(self, manager)
            self.manager = manager;
            self.observeOwnProperties;
        end
        
        % Disable listeners for this object's properties.
        % @details
        % This object will no longer report property changes to its
        % manager.
        % @details
        % Users should not use stopReportingPropertiesToManager() but
        % managers may call it internally.
        function stopReportingPropertiesToManager(self)
            self.unobserveOwnProperties;
        end
    end
    
    methods (Access=protected)
        % Delete any property listeners (used internally).
        function unobserveOwnProperties(self)
            listeners = struct2cell(self.propertyChangeListeners);
            for ii = 1:length(listeners)
                delete(listeners{ii});
            end
            self.propertyChangeListeners = struct;
        end
        
        % Create a listener for each SetObservable property (used
        % internally).
        function observeOwnProperties(self)
            self.unobserveOwnProperties;
            metaSelf = metaclass(self);
            metaProps = metaSelf.Properties;
            for ii = 1:length(metaProps)
                mp = metaProps{ii};
                if mp.SetObservable
                    self.propertyChangeListeners.(mp.Name) = ...
                        self.addlistener(mp.Name, 'PostSet', ...
                        @dotsAllManagedObjects.reportPropertyChange);
                end
            end
        end
    end
    
    methods (Static)
        % Report a property change to an object's manager.
        % @param metaProp a meta.property object that represents a property
        % of a dotsAllManagedObjects object
        % @param event an event.PropertyEvent object that represents a
        % change in a dotsAllManagedObjects object property
        % @details
        % Called internally by event listeners for the SetObservable
        % properties of dotsAllManagedObjects objects.
        % @details
        % Users should not use reportPropertyChange() directly.
        function reportPropertyChange(metaProp, event)
            self = event.AffectedObject;
            self.manager.acknowledgePropertyChange(self, metaProp.Name);
        end
    end
end